package be.jaisy.ui.httpservlet.extender.internal;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

import org.osgi.framework.Bundle;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;

import be.jaisy.osgi.IBundleContextAware;
import be.jaisy.ui.httpservlet.extender.util.ManifestParserHelper;
import be.jaisy.ui.httpservlet.extender.util.ManifestParserHelper.ResourceMappingContribution;
import be.jaisy.ui.httpservlet.extender.util.ManifestParserHelper.servletMappingContribution;

/**
 * @author Stany Blanvalet
 */
public class ServletExtenderRegistry {

	private Set<Bundle> activatedBundles;

	private HttpService httpService;

	public ServletExtenderRegistry(HttpService httpService) {
		super();
		this.httpService = httpService;
		this.activatedBundles = new HashSet<Bundle>();
	}

	public void register(Bundle bundle) {
		synchronized (this.activatedBundles) {
			if (this.httpService == null)
				return;
			if (activatedBundles.contains(bundle))
				return;
			List<servletMappingContribution> servlets = ManifestParserHelper.getInstance().getServletContribution(bundle);
			List<ResourceMappingContribution> resources = ManifestParserHelper.getInstance().getResourceContribution(bundle);
			if (servlets.isEmpty() && resources.isEmpty())
				return;
			if (this.httpService == null)
				return;
			for (servletMappingContribution contribution : servlets) {
				try {
					registerServlet(bundle, contribution.getAlias(), contribution.getClassName());
				} catch (Throwable t) {
					Activator.logError(this.getClass(), "bundle [" + bundle.getBundleId() + "].[" + bundle.getSymbolicName() + "] register servlet [" + contribution.getAlias() + "] to HttpService ["
							+ httpService.toString() + "] failed :", t);
					continue;
				}
			}
			for (ResourceMappingContribution contribution : resources) {
				try {
					registerResources(bundle, contribution.getAlias(), contribution.getBundlePath(), contribution.getDefaultPage());
				} catch (Throwable t) {
					Activator.logError(this.getClass(), "bundle [" + bundle.getBundleId() + "].[" + bundle.getSymbolicName() + "] register resources [" + contribution.getAlias() + "] to HttpService ["
							+ httpService.toString() + "] failed :", t);
					continue;
				}
			}
			activatedBundles.add(bundle);
		}
	}

	public void unregister(Bundle bundle) {
		synchronized (this.activatedBundles) {
			if (this.httpService == null)
				return;
			if (!activatedBundles.contains(bundle))
				return;
			activatedBundles.remove(bundle);
		}
		unregisterAlias(bundle);
	}

	public void close() {
		synchronized (this.activatedBundles) {
			for (Bundle bundle : this.activatedBundles) {
				unregisterAlias(bundle);
			}
			this.activatedBundles.clear();
			this.httpService = null;
		}
	}

	private void registerServlet(Bundle bundle, String alias, String className) throws IllegalArgumentException, ClassNotFoundException, InstantiationException, IllegalAccessException,
			ServletException, NamespaceException {
		if (alias == null || className == null)
			throw new IllegalArgumentException("Alias and className are mandatory");
		if (!alias.startsWith("/"))
			throw new IllegalArgumentException("Alias must start with / : " + alias);
		Class clazz = bundle.loadClass(className);
		if (clazz != null) {
			Object servlet = clazz.newInstance();
			// if(!(servlet instanceof HttpServlet))
			// throw new IllegalArgumentException("Class "+ className+" not
			// instance of "+HttpServlet.class.getName());
			if (servlet instanceof IBundleContextAware)
				((IBundleContextAware) servlet).setBundleContext(bundle.getBundleContext());
			httpService.registerServlet(alias, (HttpServlet) servlet, null, null);
		} else {
			throw new IllegalArgumentException("Cannot find class " + className);
		}
		Activator.logInfo(this.getClass(), "bundle [" + bundle.getBundleId() + "].[" + bundle.getSymbolicName() + "] register servlet [" + alias + "] to HttpService [" + httpService.toString()
				+ "] done.");
	}

	private void registerResources(Bundle bundle, String alias, String bundlePath, String defaultPage) throws IllegalArgumentException, ClassNotFoundException, InstantiationException,
			IllegalAccessException, ServletException, NamespaceException {
		if (alias == null || bundlePath == null)
			throw new IllegalArgumentException("Alias and name are mandatory");
		if (!alias.startsWith("/"))
			throw new IllegalArgumentException("Alias must start with / : " + alias);
		httpService.registerResources(alias, bundlePath, new BundleEntryHttpContext(bundle, bundlePath, defaultPage));
		Activator.logInfo(this.getClass(), "bundle [" + bundle.getBundleId() + "].[" + bundle.getSymbolicName() + "] register resources [" + alias + "] to HttpService [" + httpService.toString()
				+ "] done.");
	}

	private void unregisterAlias(Bundle bundle) {
		List<servletMappingContribution> servlets = ManifestParserHelper.getInstance().getServletContribution(bundle);
		List<ResourceMappingContribution> resources = ManifestParserHelper.getInstance().getResourceContribution(bundle);
		if (servlets.isEmpty() && resources.isEmpty())
			return;
		if (this.httpService == null)
			return;
		for (servletMappingContribution contribution : servlets) {
			try {
				unregisterAlias(bundle, contribution.getAlias());
			} catch (Throwable t) {
				Activator.logError(this.getClass(), "bundle [" + bundle.getBundleId() + "].[" + bundle.getSymbolicName() + "] unregister servlet [" + contribution.getAlias() + "] to HttpService ["
						+ httpService.toString() + "] failed :", t);
				continue;
			}
		}
		for (ResourceMappingContribution contribution : resources) {
			try {
				unregisterAlias(bundle, contribution.getAlias());
			} catch (Throwable t) {
				Activator.logError(this.getClass(), "bundle [" + bundle.getBundleId() + "].[" + bundle.getSymbolicName() + "] unregister resources [" + contribution.getAlias() + "] to HttpService ["
						+ httpService.toString() + "] failed :", t);
				continue;
			}
		}
	}

	private void unregisterAlias(Bundle bundle, String alias) {
		httpService.unregister(alias);
		Activator.logInfo(this.getClass(), "bundle [" + bundle.getBundleId() + "].[" + bundle.getSymbolicName() + "] unregister servlet [" + alias + "] from HttpService [" + httpService.toString()
				+ "] done.");
	}
}
